﻿using System;
using System.IO;
using System.IO.Ports;
using System.Threading;
using System.Timers;

namespace OG_UPDATETOOL
{
    class Controler
    {

        /// <summary>
        /// 用于通知界面的信息
        /// </summary>
        public enum InfoType
        {
            INFO,
            NOTIFY,
            WARN,
            ERROR,
            RAW,
            NONE,
        }
        public enum ConnectState
        {
            CLOSE,
            OPEN,
            CONNECTED,
            DISCONNECT,
        }

        /// <summary>
        /// 当controler发生任何通知时，会给
        /// info
        /// progressStatus
        /// infoType
        /// isSafeToOperatorDevice
        /// connectStatus赋值
        /// 并产生onControlerInfoGened事件，用于通知界面
        /// </summary>
        public string info;
        public int progressStatus;//0~100
        public InfoType infoType;
        public bool isSafeToOperatorDevice;
        public ConnectState connectState;
        BootloaderProtocolHelper.BOOTCMD recentCommand;
        enum OneClickToBurn
        {
            start,
            reset,
            eraseFlash,
            update,
            crc,
            checkcrc,
            idel
        }

        public event EventHandler<EventArgs> onControlerInfoGened;

        public SerialPort serialPort = new SerialPort();
        #region 发送查询字节的定时器
        private System.Timers.Timer timer_test = new System.Timers.Timer();
        /// <summary>
        ///  打开一个定时器，定时向串口发送查询字节，直到设备响应后，不再发送
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Test_Elapsed(object sender, ElapsedEventArgs e)
        {
            if (serialPort.IsOpen == true)
            {
                var cmd = BootloaderProtocolHelper.GetCmd(BootloaderProtocolHelper.BOOTCMD.TEST);
                serialPort.Write(cmd, 0, cmd.Length);
            }
            else
            {
                timer_test.Stop();
            }
        }
        #endregion
        #region 发送包升级包的定时器
        private System.Timers.Timer timer_sendPacket = new System.Timers.Timer();
        private byte[] timer_sendBuffer;
        private volatile int timer_sendIndex = 0;
        private volatile int timer_sendTotalLength = 0;
        int sendlen = 0;
        bool safeToSendPacket = false;
        int sendPacketTimeout = 0;
        private void Timer_sendPacket_Elapsed(object sender, ElapsedEventArgs e)
        {
            this.infoType = InfoType.NONE;
            this.progressStatus = timer_sendIndex * 100 / timer_sendTotalLength;
            this.onControlerInfoGened?.Invoke(this, new EventArgs());

            if (timer_sendIndex == timer_sendTotalLength)
            {
                this.timer_sendPacket.Stop();
                var stop = BootloaderProtocolHelper.GetCmd(BootloaderProtocolHelper.BOOTCMD.END_BURN);
                Thread.Sleep(200);
                this.serialPort.Write(stop, 0, stop.Length);
                timer_sendBuffer = null;
                return;
            }
            if (safeToSendPacket == true)
            {
                sendPacketTimeout = 100;
                safeToSendPacket = false;
                if (timer_sendIndex > (timer_sendTotalLength - 128))
                {
                    sendlen = timer_sendTotalLength - timer_sendIndex;
                }
                else
                {
                    sendlen = 128;
                }
                serialPort.Write(timer_sendBuffer, timer_sendIndex, sendlen);
                timer_sendIndex += sendlen;
            }
            else
            {

                if (sendPacketTimeout < 0)
                {
                    this.timer_sendPacket.Stop();
                    this.info = "烧录失败！";
                    this.infoType = InfoType.ERROR;
                    onControlerInfoGened?.Invoke(this, new EventArgs());
                }
                else
                {
                    sendPacketTimeout--;
                }
            }

        }
        #endregion
        OneClickToBurn oneClickToBurn = OneClickToBurn.idel;
        string onnClickBurnFileName;
        ushort oneClickBurnCrc;
        #region 一键烧录功能定时器
        private System.Timers.Timer timer_oneClickToBurn = new System.Timers.Timer();
        private void Timer_oneClickToBurn_Elapsed(object sender, ElapsedEventArgs e)
        {
            switch (oneClickToBurn)
            {
                case OneClickToBurn.start:
                    this.reboot();
                    oneClickToBurn = OneClickToBurn.reset;
                    break;
                case OneClickToBurn.reset:
                    if(recentCommand == BootloaderProtocolHelper.BOOTCMD.TEST)
                    {
                        this.erase();
                        oneClickToBurn = OneClickToBurn.eraseFlash;
                    }
                    break;
                case OneClickToBurn.eraseFlash:
                    if(recentCommand == BootloaderProtocolHelper.BOOTCMD.ERASE_FLASH)
                    {
                        this.burn(onnClickBurnFileName);
                        oneClickToBurn = OneClickToBurn.update;
                    }
                    break;
                case OneClickToBurn.update:
                    if(recentCommand == BootloaderProtocolHelper.BOOTCMD.END_BURN)
                    {
                        this.crcWrite(oneClickBurnCrc);
                        oneClickToBurn = OneClickToBurn.crc;
                    }
                    else if(timer_sendPacket.Enabled == false)
                    {
                        break;
                    }
                    break;
                case OneClickToBurn.crc:
                    if (recentCommand == BootloaderProtocolHelper.BOOTCMD.CRC)
                    {
                        this.crcCheck();
                        oneClickToBurn = OneClickToBurn.checkcrc;
                    }
                    break;
                case OneClickToBurn.checkcrc:
                    if (recentCommand == BootloaderProtocolHelper.BOOTCMD.CRC_CHECK)
                    {
                        oneClickToBurn = OneClickToBurn.idel;
                    }
                    break;
                case OneClickToBurn.idel:
                    this.timer_oneClickToBurn.Stop();
                    break;
                default:
                    break;
            }
        }
        #endregion

        public Controler()
        {

            serialPort.DataReceived += SerialPort_DataReceived;

            timer_test.Elapsed += Test_Elapsed;
            timer_test.Interval = 1000;
            timer_test.Stop();

            timer_sendPacket.Elapsed += Timer_sendPacket_Elapsed;
            timer_sendPacket.Interval = 50;
            timer_sendPacket.Stop();
            isSafeToOperatorDevice = true;

            timer_oneClickToBurn.Elapsed += Timer_oneClickToBurn_Elapsed;
            timer_oneClickToBurn.Interval = 300;
            timer_oneClickToBurn.Stop();
        }

        

        //定时向端口发送TEST包，直到收到返回值
        public void Start(string port)
        {
            try
            {
                this.isSafeToOperatorDevice = false;
                if (serialPort.IsOpen == true)
                {
                    throw new Exception("端口已经打开！");
                }
                serialPort.PortName = port;
                serialPort.Open();
                this.info = "端口打开成功，等待设备连接";
                this.infoType = InfoType.NOTIFY;
                this.connectState = ConnectState.OPEN;
                onControlerInfoGened?.Invoke(this, new EventArgs());
                timer_test.Start();//打开端口成功后，进行一次测试
            }
            catch (Exception e)
            {
                this.info = e.Message + "\n";
                this.infoType = InfoType.ERROR;
                this.connectState = ConnectState.CLOSE;
                onControlerInfoGened?.Invoke(this, new EventArgs());
            }
        }
        public void Stop()
        {
            try
            {
                if (serialPort.IsOpen == true)
                {
                    serialPort.Close();
                    this.info = "端口关闭。\n";
                    this.infoType = InfoType.INFO;
                    this.connectState = ConnectState.CLOSE;
                    onControlerInfoGened?.Invoke(this, new EventArgs());
                }
            }
            catch (Exception e)
            {
                this.info = e.Message + "\n";
                this.infoType = InfoType.ERROR;
                onControlerInfoGened?.Invoke(this, new EventArgs());
            }
        }

        #region 接收到数据包的类别操作
        /// <summary>
        /// 当收到来自设备的数据包以后，会根据不同的包类型给出不同的信息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            BootloaderProtocolHelper.BOOTCMD cmd;
            int b2r = serialPort.BytesToRead;
            do
            {
                Thread.Sleep(50);
                if (b2r != serialPort.BytesToRead)
                {
                    b2r = serialPort.BytesToRead;
                }
                else
                {
                    break;
                }
            } while (true);
            var length = serialPort.BytesToRead;

            byte[] t = new byte[length];
            serialPort.Read(t, 0, length);
            if (t.Length < 3)
            {
                return;
            }
            if (BootloaderProtocolHelper.byteToCmd(out cmd, t))
            {
                timer_test.Stop();
                switch (cmd)
                {
                    case BootloaderProtocolHelper.BOOTCMD.BOOT_INFO:
                        recentCommand = cmd;
                        this.isSafeToOperatorDevice = true;
                        this.infoType = InfoType.INFO;
                        this.info = "设备信息:\n";
                        var tmp = BootloaderProtocolHelper.GetInfo(t);
                        this.info += tmp;
                        this.onControlerInfoGened?.Invoke(this, new EventArgs());
                        break;
                    case BootloaderProtocolHelper.BOOTCMD.TEST:
                        recentCommand = cmd;
                        this.isSafeToOperatorDevice = true;
                        this.infoType = InfoType.NOTIFY;
                        this.connectState = ConnectState.CONNECTED;
                        this.info = "连接成功";
                        this.onControlerInfoGened?.Invoke(this, new EventArgs());
                        break;
                    case BootloaderProtocolHelper.BOOTCMD.ERASE_FLASH:
                        recentCommand = cmd;
                        this.infoType = InfoType.NOTIFY;
                        this.connectState = ConnectState.CONNECTED;
                        this.info = "擦除成功";
                        this.onControlerInfoGened?.Invoke(this, new EventArgs());
                        break;
                    case BootloaderProtocolHelper.BOOTCMD.BURN:
                        recentCommand = cmd;
                        if (this.timer_sendBuffer == null)
                        {
                            break;
                        }
                        safeToSendPacket = true;
                        sendPacketTimeout = 100;
                        this.isSafeToOperatorDevice = false;
                        this.infoType = InfoType.INFO;
                        this.connectState = ConnectState.CONNECTED;
                        Console.WriteLine("烧录大小:" + timer_sendBuffer.Length.ToString());
                        this.info = "烧录大小:" + timer_sendBuffer.Length.ToString() + "\n开始烧录！如意外断开设备或烧录失败，请重启设备后再重新连接!";
                        this.onControlerInfoGened?.Invoke(this, new EventArgs());
                        this.timer_sendPacket.Start();
                        break;
                    case BootloaderProtocolHelper.BOOTCMD.END_BURN:
                        recentCommand = cmd;
                        this.isSafeToOperatorDevice = true;
                        this.infoType = InfoType.NOTIFY;
                        this.connectState = ConnectState.CONNECTED;
                        this.info = "烧录完毕";
                        this.progressStatus = 0;
                        this.onControlerInfoGened?.Invoke(this, new EventArgs());
                        break;
                    case BootloaderProtocolHelper.BOOTCMD.BURN_ACK:
                        recentCommand = cmd;
                        safeToSendPacket = true;
                        break;
                    case BootloaderProtocolHelper.BOOTCMD.CRC:
                        recentCommand = cmd;
                        this.infoType = InfoType.NOTIFY;
                        this.connectState = ConnectState.CONNECTED;
                        this.info = "CRC写入成功";
                        this.onControlerInfoGened?.Invoke(this, new EventArgs());
                        break;
                    case BootloaderProtocolHelper.BOOTCMD.CRC_CHECK:
                        recentCommand = cmd;
                        this.connectState = ConnectState.CONNECTED;
                        this.info = "CRC校验";
                        if (BootloaderProtocolHelper.GetCrcResult(t) == true)
                        {
                            this.infoType = InfoType.NOTIFY;
                            this.info += "成功！";
                        }
                        else
                        {
                            this.infoType = InfoType.ERROR;
                            this.info += "失败！";
                        }

                        this.onControlerInfoGened?.Invoke(this, new EventArgs());
                        break;
                    case BootloaderProtocolHelper.BOOTCMD.REBOOT:
                        recentCommand = cmd;
                        this.isSafeToOperatorDevice = false;
                        this.infoType = InfoType.WARN;
                        this.connectState = ConnectState.DISCONNECT;
                        this.timer_test.Start();//重启后，开始检测设备连接
                        this.info = "设备复位成功,等待设备连接";
                        this.onControlerInfoGened?.Invoke(this, new EventArgs());
                        break;
                    case BootloaderProtocolHelper.BOOTCMD.JUMP_APP:
                        recentCommand = cmd;
                        this.infoType = InfoType.WARN;
                        this.connectState = ConnectState.DISCONNECT;
                        this.info = "设备跳转至APP成功，重新连接请复位设备";
                        this.onControlerInfoGened?.Invoke(this, new EventArgs());
                        break;
                    default:
                        break;
                }
            }


        }

        #endregion

        #region 操作命令
        /// <summary>
        /// 用于计算指定文件的CRC16值
        /// </summary>
        /// <param name="file"></param>
        /// <returns></returns>
        public int CaclCrc16(string file)
        {
            byte[] buffer = File.ReadAllBytes(file);
            return Crc16.crc16(buffer);
        }
        /// <summary>
        /// 此命令会发送一个烧录命令
        /// 从机收到包后，会返回一个接收到升级指令成功消息
        /// 上位机收到后，会立即发送升级包
        /// 最后升级完成再发送一个结束包
        /// 具体可以查看升级的定时器函数。
        /// 每次升级包发出后，从机必须有确认包，主机接收，且只接收一次。
        /// 接收不到直接认为升级失败。
        /// 该方式只适合本地升级，协议也很简单。
        /// </summary>
        /// <param name="file"></param>
        public void burn(string file)
        {
            if (file.Length == 0)
            {
                return;
            }
            this.info = "烧录";
            this.infoType = InfoType.INFO;
            onControlerInfoGened?.Invoke(this, new EventArgs());

            this.isSafeToOperatorDevice = false;
            timer_sendIndex = 0;
            var cmd = BootloaderProtocolHelper.GetCmd(BootloaderProtocolHelper.BOOTCMD.BURN);
            timer_sendBuffer = File.ReadAllBytes(file);
            timer_sendTotalLength = timer_sendBuffer.Length;
            serialPort.Write(cmd, 0, cmd.Length);

        }

        /// <summary>
        /// 发送命令后，从机擦除flash完成后报告主机
        /// </summary>
        public void erase()
        {
            this.info = "擦除";
            this.infoType = InfoType.INFO;
            onControlerInfoGened?.Invoke(this, new EventArgs());
            var erase = BootloaderProtocolHelper.GetCmd(BootloaderProtocolHelper.BOOTCMD.ERASE_FLASH);
            serialPort.Write(erase, 0, erase.Length);
        }

        public void reboot()
        {
            this.info = "复位";
            this.infoType = InfoType.INFO;
            onControlerInfoGened?.Invoke(this, new EventArgs());
            var reboot = BootloaderProtocolHelper.GetCmd(BootloaderProtocolHelper.BOOTCMD.REBOOT);
            serialPort.Write(reboot, 0, reboot.Length);
        }

        public void jumpToApp()
        {
            this.info = "跳转";
            this.infoType = InfoType.INFO;
            onControlerInfoGened?.Invoke(this, new EventArgs());
            var jta = BootloaderProtocolHelper.GetCmd(BootloaderProtocolHelper.BOOTCMD.JUMP_APP);
            serialPort.Write(jta, 0, jta.Length);
        }

        public void crcWrite(UInt16 crc)
        {
            this.info = "CRC烧录";
            this.infoType = InfoType.INFO;
            onControlerInfoGened?.Invoke(this, new EventArgs());
            var cw = BootloaderProtocolHelper.GetCmd(BootloaderProtocolHelper.BOOTCMD.CRC, crc);
            serialPort.Write(cw, 0, cw.Length);
        }

        public void crcCheck()
        {
            this.info = "CRC校验";
            this.infoType = InfoType.INFO;
            onControlerInfoGened?.Invoke(this, new EventArgs());
            var gcc = BootloaderProtocolHelper.GetCmd(BootloaderProtocolHelper.BOOTCMD.CRC_CHECK);
            serialPort.Write(gcc, 0, gcc.Length);
        }

        public void getBootInfo()
        {
            this.info = "获取设备信息";
            this.infoType = InfoType.INFO;
            onControlerInfoGened?.Invoke(this, new EventArgs());
            var gbi = BootloaderProtocolHelper.GetCmd(BootloaderProtocolHelper.BOOTCMD.BOOT_INFO);
            serialPort.Write(gbi, 0, gbi.Length);
        }

        
        public bool isOneClickBurnStart
        {
            get
            {
                return timer_oneClickToBurn.Enabled;
            }
        }
        /// <summary>
        /// 一键烧录功能
        /// 点击后，设备将依次执行,只是方便操作。
        /// 复位设备
        /// 擦除flash
        /// 发送升级包
        /// 烧录crc
        /// 验证crc步骤
        /// </summary>
        public void oneClickBurnStart(string fileName)
        {
            this.onnClickBurnFileName = fileName;
            this.oneClickBurnCrc = (UInt16)this.CaclCrc16(fileName);
            this.info = "开始一键烧录";
            this.infoType = InfoType.INFO;
            onControlerInfoGened?.Invoke(this, new EventArgs());
            this.timer_oneClickToBurn.Start();
            this.oneClickToBurn = OneClickToBurn.start;
        }

        public void oneClickBurnStop()
        {
            this.info = "停止一键烧录";
            this.infoType = InfoType.INFO;
            onControlerInfoGened?.Invoke(this, new EventArgs());
            this.timer_oneClickToBurn.Stop();
        }
        #endregion
    }
}
